#include "Avalon/IO/SizeDeclarativeReader.hpp"
#include <boost/bind.hpp>
#include "Avalon/IO/Buffer.hpp"

using namespace Avalon;
using namespace Avalon::IO;
using namespace Avalon::Threading;
using namespace boost;
using namespace std;

namespace {
  static const unsigned int SIZE_OF_INT = 4;
}

SizeDeclarativeReader::SizeDeclarativeReader(Carry<Reader> source)
    : m_source(source),
      m_sizeRemaining(0) {}

SizeDeclarativeReader::~SizeDeclarativeReader() {}

bool SizeDeclarativeReader::IsAtEndOfDataSource() {
  return m_source->IsAtEndOfDataSource();
}

Async<int>::Ptr SizeDeclarativeReader::Read(void* destination, int size) {
  lock_guard<recursive_mutex> lock(m_mutex);
  assert(m_sourceRead == NULL);
  if(m_sizeRemaining != 0) {
    boost::uint32_t sizeToRead = min(static_cast<boost::uint32_t>(size),
      m_sizeRemaining);
    m_sourceRead = m_source->Read(destination, static_cast<int>(sizeToRead));
    AsyncOwner<int>::Ptr parsedRead = AsyncOwner<int>::Create();
    m_sourceRead->ConnectFinishedSignal(bind(&SizeDeclarativeReader::OnDataRead,
      this, destination, sizeToRead, sizeToRead, parsedRead, m_sourceRead));
    return parsedRead;
  }
  m_sizeReadIterator = reinterpret_cast<char*>(&m_sizeRemaining);
  m_sourceRead = m_source->Read(m_sizeReadIterator, SIZE_OF_INT);
  AsyncOwner<int>::Ptr parsedRead = AsyncOwner<int>::Create();
  m_sourceRead->ConnectFinishedSignal(bind(&SizeDeclarativeReader::OnSizeRead,
    this, static_cast<Buffer*>(NULL), destination, size, parsedRead,
    m_sourceRead));
  return parsedRead;
}

Async<int>::Ptr SizeDeclarativeReader::Read(Buffer* destination, int size) {
  lock_guard<recursive_mutex> lock(m_mutex);
  assert(m_sourceRead == NULL);
  if(m_sizeRemaining != 0) {
    int sizeToRead = min(size, static_cast<int>(m_sizeRemaining));
    destination->Reserve(sizeToRead);
    m_sourceRead = m_source->Read(destination, sizeToRead);
    AsyncOwner<int>::Ptr parsedRead = AsyncOwner<int>::Create();
    m_sourceRead->ConnectFinishedSignal(bind(&SizeDeclarativeReader::OnDataRead,
      this, destination->GetMutableData(), sizeToRead, sizeToRead, parsedRead,
      m_sourceRead));
    return parsedRead;
  }
  m_sizeReadIterator = reinterpret_cast<char*>(&m_sizeRemaining);
  m_sourceRead = m_source->Read(m_sizeReadIterator, SIZE_OF_INT);
  AsyncOwner<int>::Ptr parsedRead = AsyncOwner<int>::Create();
  m_sourceRead->ConnectFinishedSignal(bind(&SizeDeclarativeReader::OnSizeRead,
    this, destination, destination->GetMutableData(), size, parsedRead,
    m_sourceRead));
  return parsedRead;
}

void SizeDeclarativeReader::OnSizeRead(Buffer* buffer, void* destination,
    int size, const AsyncOwner<int>::Ptr& parsedRead,
    const Async<int>::Ptr& sourceRead) {
  lock_guard<recursive_mutex> lock(m_mutex);
  m_sourceRead.reset();
  if(sourceRead->GetState() == BaseAsync::EXCEPTION) {
    parsedRead->CopyException(sourceRead);
    return;
  }
  m_sizeReadIterator += sourceRead->Get();
  if(m_sizeReadIterator < reinterpret_cast<char*>(&m_sizeRemaining) +
      SIZE_OF_INT) {
    m_sourceRead = m_source->Read(m_sizeReadIterator, SIZE_OF_INT);
    m_sourceRead->ConnectFinishedSignal(bind(&SizeDeclarativeReader::OnSizeRead,
      this, buffer, destination, size, parsedRead, m_sourceRead));
    return;
  }
  if(IsBigEndian()) {
    m_sizeRemaining = ReverseBytes(m_sizeRemaining);
  }
  int sizeToRead = min(static_cast<int>(m_sizeRemaining), size);
  if(buffer != NULL) {
    buffer->Reserve(sizeToRead);
    destination = buffer->GetMutableData();
  }
  m_sourceRead = m_source->Read(destination, sizeToRead);
  m_sourceRead->ConnectFinishedSignal(bind(&SizeDeclarativeReader::OnDataRead,
    this, destination, sizeToRead, sizeToRead, parsedRead, m_sourceRead));
}

void SizeDeclarativeReader::OnDataRead(void* destination,
    boost::uint32_t totalSize, boost::uint32_t sizeToRead,
    const AsyncOwner<int>::Ptr& parsedRead, const Async<int>::Ptr& sourceRead) {
  lock_guard<recursive_mutex> lock(m_mutex);
  m_sourceRead.reset();
  if(sourceRead->GetState() == BaseAsync::EXCEPTION) {
    parsedRead->CopyException(sourceRead);
    return;
  }
  sizeToRead -= sourceRead->Get();
  m_sizeRemaining -= sourceRead->Get();
  if(sizeToRead == 0) {
    parsedRead->SetResult(totalSize);
    return;
  }
  m_sourceRead = m_source->Read(static_cast<char*>(destination) +
    (totalSize - sizeToRead), sizeToRead);
  m_sourceRead->ConnectFinishedSignal(bind(&SizeDeclarativeReader::OnDataRead,
    this, destination, totalSize, sizeToRead, parsedRead, m_sourceRead));
}
